package repository

import (
	"context"

	"gitee.com/flycash/simple-im/internal/domain"
	"gitee.com/flycash/simple-im/internal/repository/dao"
	"github.com/ecodeclub/ekit/slice"
)

var (
	ErrUserDuplicate = dao.ErrUserDuplicate
	ErrUserNotFound  = dao.ErrDataNotFound
)

//go:generate mockgen -source=user.go -package=repositorymocks -destination=mocks/user.mock.go UserRepository
type UserRepository interface {
	Create(ctx context.Context, u domain.User) (int64, error)
	// Update 更新数据，只有非零值才会更新
	Update(ctx context.Context, u domain.User) error
	FindByEmail(ctx context.Context, email string) (domain.User, error)
	FindByID(ctx context.Context, id int64) (domain.User, error)
	FindByIDs(ctx context.Context, ids []int64) ([]domain.User, error)
	FindAll(ctx context.Context) ([]domain.User, error)
	FindByLimitOffset(ctx context.Context, limit int, offset int) (int64, []domain.User, error)
}

type DBUserRepository struct {
	dao dao.UserDAO
}

func NewUserRepository(d dao.UserDAO) UserRepository {
	return &DBUserRepository{
		dao: d,
	}
}

func (ur *DBUserRepository) FindByIDs(ctx context.Context, ids []int64) ([]domain.User, error) {
	usrs, err := ur.dao.FindByIDs(ctx, ids)
	if err != nil {
		return nil, err
	}
	return slice.Map(usrs, func(_ int, src dao.User) domain.User {
		return userToDomain(src)
	}), nil
}

func (ur *DBUserRepository) Create(ctx context.Context, u domain.User) (int64, error) {
	return ur.dao.Insert(ctx, ur.domainToEntity(u))
}

func (ur *DBUserRepository) Update(ctx context.Context, u domain.User) error {
	return ur.dao.UpdateNonZeroFields(ctx, ur.domainToEntity(u))
}

func (ur *DBUserRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {
	u, err := ur.dao.FindByEmail(ctx, email)
	if err != nil {
		return domain.User{}, err
	}
	return userToDomain(u), err
}

func (ur *DBUserRepository) FindByID(ctx context.Context, id int64) (domain.User, error) {
	ue, err := ur.dao.FindByID(ctx, id)
	if err != nil {
		return domain.User{}, err
	}
	return userToDomain(ue), nil
}

func (ur *DBUserRepository) FindAll(ctx context.Context) ([]domain.User, error) {
	usrs, err := ur.dao.FindAll(ctx)
	if err != nil {
		return nil, err
	}
	return slice.Map[dao.User, domain.User](usrs, func(_ int, src dao.User) domain.User {
		return userToDomain(src)
	}), nil
}

func (ur *DBUserRepository) FindByLimitOffset(ctx context.Context, limit, offset int) (int64, []domain.User, error) {
	count, err := ur.dao.Count(ctx)
	if err != nil {
		return 0, nil, err
	}
	if count == 0 {
		return 0, nil, nil
	}
	usrs, err := ur.dao.FindByLimitOffset(ctx, limit, offset)
	if err != nil {
		return 0, nil, err
	}
	return count, slice.Map[dao.User, domain.User](usrs, func(_ int, src dao.User) domain.User {
		return userToDomain(src)
	}), nil
}

func (ur *DBUserRepository) domainToEntity(u domain.User) dao.User {
	return dao.User{
		ID:       u.ID,
		Nickname: u.Nickname,
		Avatar:   u.Avatar,
		Token:    u.Token,
	}
}

func userToDomain(ue dao.User) domain.User {
	return domain.User{
		ID:       ue.ID,
		Nickname: ue.Nickname,
		Avatar:   ue.Avatar,
		Token:    ue.Token,
		Ctime:    ue.Ctime,
	}
}
